package jmpo.cls;

final class OuterClass {
    private String outerName;

    OuterClass(String outerName) {
        this.outerName = outerName;
    }

    private static String toUpper(String s) {
        return s.toUpperCase();
    }
    /**
     * Класс, объявленный внутри другого класса называется NestedClass,
     * он может быть статическим и его объекты могут создаваться 
     * отдельно от объектов включающего класса.
     * Такой класс может иметь модификаторы доступа:
     * private, protected, package, public.
     */
    private static final class NestedClass {
        NestedClass(String name) {
            this.name = name;
        }

        String getName() { 
            // мы можем свободно использовать статические методы внешнего класса
            // внутри Static Nested Class, даже, если они объявлены private 
            return toUpper(name); 
        }

        private final String name;
    }

    /**
     * Для класса с модификатором private его объекты нельзя создать
     * извне класса, но можно создать, например, в статическом методе.
     */
    public static NestedClass nestedClassFactoryMethod(String name) {
        return new NestedClass(name);
    }

    /**
     * Хотя мы и можем теперь создавать объекты внутреннего класса,
     * но мы не можем вызвать их методы.
     */
    public static boolean nestedClassNameEq(NestedClass obj, String name) {
        return obj.getName().equals(name);
    }
    /**
     * Пример выше является демонстрационным. Если StaticNestedClass объявляется 
     * как private, обычно это делается как часть внутренней реализации, ссылки 
     * на объекты такого класса не должны покидать пределы класса.
     */

    /**
     * Внутренний класс, объявленный без слова static,
     * связывается с объектом внешнего класса.
     * Для создания класса InnerClass необходим объект OuterClass.
     * Используется специальный синтаксис:
     *     outerObject.new Innerclass(...)
     */
    class InnerClass {
        InnerClass(String name) {
            this.name = name;
        }

        String getName() {
            return name + " " + outerName;
        }

        void setOuterName(String name) {
            outerName = name;
        }

        private String name;
    }
}

public class NestedClasses {
    private static boolean staticNestedClassTest() {
        var nestedObj = OuterClass.nestedClassFactoryMethod("Name");
        return OuterClass.nestedClassNameEq(nestedObj, "NAME");
    }

    private static boolean innerNestedClassTest() {
        var outerObject = new OuterClass("OFF");
        var innerObjectA = outerObject.new InnerClass("A");
        var innerObjectB = outerObject.new InnerClass("B");
        innerObjectA.setOuterName("ON");
        return 
            innerObjectA.getName().equals("A ON")
            && innerObjectB.getName().equals("B ON");
    }

    private static void test(boolean expectedTrue) {
        if (expectedTrue) {
            System.out.println("Ok!");
            return;
        }
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        test(staticNestedClassTest());
        test(innerNestedClassTest());
    }
}
